﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace AutomatedTP
{
    /// <summary>
    /// Class for parsing XML source files and store required data into test procedure
    /// </summary>
    class Parse
    {
        String module = "!!!";

        /// <summary>
        /// Parse_xmls with the specified opt.
        /// </summary>
        /// <param name="opt">The options from command line.</param>
        /// <param name="tp_one">The tp_one link to test procedure to store parsed data.</param>
        /// <returns></returns>
        public int parse_xml(Options opt, ref TP tp_one)
        {
            Logger.WriteToLog(3, "Parsing started!");

            for (int i = 0; i < opt.Items.Count; i++)
                {                    
                    TC tc_one = new TC();
                    tp_one.AddTestCase(tc_one);


                    try
                    {
                        global_var.set_testCase(opt.Items[i]);
                    }
                    catch (Exception ex)
                    {
                        Logger.WriteToLog(3, ex.ToString());
                        Logger.WriteToLog(3, "Bad file specified as test case XML!");
                        return 1;
                    }
            

                    //just for testing run on two TC and change name for second TC ... 
                    if (i == 0)
                    {
                        Logger.WriteToLog(1, "First item parsing and setting up sources");       
                        
                        
                        try
                        {
                            module = (String)global_var.testCase.Element("tc").Element("module");
                        }
                        catch (Exception ex)
                        {
                            Logger.WriteToLog(3, ex.ToString());
                            Logger.WriteToLog(3, "Bad file specified as module!");
                            return 1;
                        }

                        try
                        {
                            global_var.set_XML_module(module);
                            Logger.WriteToLog(2, "Module slected: " + module);
                        }
                        catch (Exception ex)
                        {
                            Logger.WriteToLog(3, ex.ToString());
                            Logger.WriteToLog(3, "Bad file specified as module!");
                            return 1;
                        }                        
                        tp_one.SetModule(module);

                        String m_test = (String)global_var.XML_module.Element("module").Attribute("name");
                        if (!m_test.Equals(module))
                        {
                            Logger.WriteToLog(3, "Bad file specified as module!");
                            return 1;
                        }                        
                                                                       
                    }

                    string tmp_module = (String)global_var.testCase.Element("tc").Element("module");
                    if (!module.Equals(tmp_module))
                    {
                        Logger.WriteToLog(3, i+1 + ". TC is from different module!");
                        return 1;
                    } 

                    //extracting author of TC
                    String author = global_var.testCase.Element("tc").Element("author").Value;
                    tc_one.SetAuthor(author);

                    //String header = " * Created: today, user: ";
                    //header += author;

                    //extracting ltp-id or name
                    //extract from parameter!
                    String ltp_name = global_var.testCase.Element("tc").Element("ltp-id").Value;
                    tp_one.SetName(ltp_name);

                    String description = " * Description: Low level test procedure ";
                    description += ltp_name;                    
                    
                    String ltc_id = global_var.testCase.Element("tc").Element("id").Value;
                    String trace = global_var.testCase.Element("tc").Element("satisfies").Value;
                    tc_one.SetName(ltc_id);
                    tc_one.SetTrace(trace);
                    Logger.WriteToLog(2, "Started sub-procedure: " + ltc_id);

                    //extract and handle all stubs.
                    var stubs = global_var.XML_module.Element("module").Elements("stub_func");
                    foreach (var stub in stubs)
                    {
                        string stub_name = "";
                        //string stub_type = null;
                        string stub_ret = null;
                        string stub_ret_type = null;
                        Dictionary<string, string> stub_inputs = new Dictionary<string, string>();

                        //parsing out information about all stubs from module.
                        stub_name = stub.Element("name").Value;
                        if (stub.Element("func_ret").Attribute("void").Value.Equals("false"))
                        {
                            stub_ret_type = stub.Element("func_ret").Value;
                        }
                        if (stub.Element("func_param").Attribute("void").Value.Equals("false"))
                        {
                            var stub_in = stub.Element("func_param").Elements("param");
                            foreach (var s_i in stub_in)
                            {
                                stub_inputs.Add(s_i.Value, s_i.Attribute("type").Value);
                                Logger.WriteToLog(1, "Stub with param: " + stub_inputs[s_i.Value] + " " + s_i.Value);
                            }
                        }                                                
                        Stub stubik = new Stub(stub_name, stub_ret, stub_ret_type, stub_inputs.Count, stub_inputs);
                        Logger.WriteToLog(1, "Stub loaded: " + stub_name + " count: " + stub_inputs.Count + " ret: " + stub_ret_type);

                        //adding operations for stub
                        var func_stub = global_var.testCase.Element("tc").Element("stubs").Elements("func");
                        if (!tp_one.GetManual())
                        {
                            foreach (var fstub in func_stub)
                            {
                                if (fstub.Attribute("name").Value.Equals(stub_name))
                                {
                                    //extract MODIFY operations
                                    var modify_call = fstub.Element("modify").Elements("call");
                                    foreach (var tmp in modify_call)
                                    {
                                        Modify modify_tmp = ExtractModify(tp_one, stub_name, tmp);
                                        stubik.AddOp(modify_tmp);
                                        ExtractGlobal(tp_one, modify_tmp.GetVariable());
                                    }
                                    //extract CHECK operations
                                    var check_call = fstub.Element("check").Elements("call");
                                    foreach (var tmp in check_call)
                                    {
                                        Check check_tmp = ExtractCheck(tp_one, stub_name, tmp);
                                        stubik.AddOp(check_tmp);
                                        ExtractGlobal(tp_one, check_tmp.GetVariable());
                                    }
                                    //extract RETURN operations
                                    var return_call = fstub.Element("return").Elements("call");
                                    foreach (var tmp in return_call)
                                    {
                                        Return return_tmp = ExtractReturn(tp_one, stub_name, tmp);
                                        stubik.AddOp(return_tmp);
                                    }
                                }
                            }
                        }
                        //finaly add whole stub to TC
                        tc_one.AddStub(stubik);
                    }

                    //=======WRAPS
                    //extracting and adding wraps will be similar. Maybe would be fine to think about some mutual method.
                    var func_wrap = global_var.testCase.Element("tc").Element("wraps").Elements("func");
                    foreach (var fwrap in func_wrap)
                    {
                        string name = "";
                        string ret_type = null;
                        Dictionary<string, string> input = new Dictionary<string, string>();
                        foreach (var tmp in global_var.XML_module.Element("module").Elements("global_func"))
                        {
                            if (tmp.Element("name").Value.Equals(fwrap.Attribute("name").Value))
                            {
                                name += tmp.Element("name").Value;
                                if (tmp.Element("func_ret").Attribute("void").Value.Equals("false"))
                                {
                                    ret_type = tmp.Element("func_ret").Value;
                                }
                                if (tmp.Element("func_param").Attribute("void").Value.Equals("false"))
                                {
                                    foreach (var param in tmp.Element("func_param").Elements("param"))
                                    {
                                        input.Add(param.Value, param.Attribute("type").Value);
                                    }
                                }
                                break;
                            }
                        }

                        Wrap wrap = new Wrap(name, null, ret_type, input.Count, input);
                        if (!tp_one.GetManual())
                        {
                            //===BEFORE===
                            //before modify
                            foreach (var before_m in fwrap.Element("before").Element("modify").Elements("call"))
                            {
                                Modify modify_tmp = ExtractModify(tp_one, name, before_m);
                                wrap.AddBefore(modify_tmp);
                                ExtractGlobal(tp_one, modify_tmp.GetVariable());
                            }
                            //before check
                            foreach (var before_c in fwrap.Element("before").Element("check").Elements("call"))
                            {
                                Check check_tmp = ExtractCheck(tp_one, name, before_c);
                                wrap.AddBefore(check_tmp);
                                ExtractGlobal(tp_one, check_tmp.GetVariable());
                            }
                            //===REPLACE===
                            if (fwrap.Element("replace").Attribute("original").Value.Equals("no"))
                            {
                                wrap.orig_call = false;
                                //replace modify
                                foreach (var replace_m in fwrap.Element("replace").Element("modify").Elements("call"))
                                {
                                    Modify modify_tmp = ExtractModify(tp_one, name, replace_m);
                                    wrap.AddReplace(modify_tmp);
                                    ExtractGlobal(tp_one, modify_tmp.GetVariable());
                                }
                                //replace check
                                foreach (var replace_c in fwrap.Element("replace").Element("check").Elements("call"))
                                {
                                    Check check_tmp = ExtractCheck(tp_one, name, replace_c);
                                    wrap.AddReplace(check_tmp);
                                    ExtractGlobal(tp_one, check_tmp.GetVariable());
                                }
                            }
                            else
                            {
                                wrap.orig_call = true;
                            }
                            //===AFTER===
                            //after modify
                            foreach (var after_m in fwrap.Element("after").Element("modify").Elements("call"))
                            {
                                Modify after_tmp = ExtractModify(tp_one, name, after_m);
                                wrap.AddAfter(after_tmp);
                                ExtractGlobal(tp_one, after_tmp.GetVariable());
                            }
                            //after check
                            foreach (var after_c in fwrap.Element("after").Element("check").Elements("call"))
                            {
                                Check after_tmp = ExtractCheck(tp_one, name, after_c);
                                wrap.AddAfter(after_tmp);
                                ExtractGlobal(tp_one, after_tmp.GetVariable());
                            }
                            //after return
                            foreach (var after_r in fwrap.Element("after").Element("return").Elements("call"))
                            {
                                Return after_tmp = ExtractReturn(tp_one, name, after_r);
                                wrap.AddAfter(after_tmp);
                                ExtractGlobal(tp_one, after_tmp.GetVariable());
                            } 
                        }
                        //finaly add whole wrap to TC
                        tc_one.AddWrap(wrap);
                    }

                    Tproc tproc_one = new Tproc();
                    tc_one.SetProc(tproc_one);

                    tproc_one.tested_func = global_var.testCase.Element("tc").Element("tested_func").Value;

                    foreach (var tmp_func in global_var.XML_module.Element("module").Element("local_func").Elements("name"))
                    {
                        Func func = new Func(tmp_func.Value);
                        tp_one.AddLocal(func);
                    }

                    if (!tp_one.GetManual())
                    {
                        //extract tproc information.                        
                        var inputs_var = global_var.testCase.Element("tc").Element("inputs").Elements("variable");
                        foreach (var tmp in inputs_var)
                        {
                            Variable reg_tmp = ExtractVariable(tmp);
                            tproc_one.AddInitGlobVar(reg_tmp);
                            ExtractGlobal(tp_one, reg_tmp);
                            //missing different types like pointers, arrays or structures!!!                        
                        }

                        //extract expected calling sequence
                        var exp_seq = global_var.testCase.Element("tc").Element("Exp-out").Element("calling-sequence").Elements("name");
                        foreach (var tmp in exp_seq)
                        {
                            int times;
                            int.TryParse(tmp.Attribute("times").Value, out times);
                            tproc_one.AddCall(tmp.Value, times);
                        }

                        //defined order
                        var order = global_var.testCase.Element("tc").Element("Exp-out").Element("calling-sequence").Attribute("defined_orde").Value;
                        if (order.Equals("yes"))
                        {
                            tproc_one.defined_order = true;
                        }
                        else
                        {
                            tproc_one.defined_order = false;
                        }                        

                        //extract expected output
                        var exp_out = global_var.testCase.Element("tc").Element("Exp-out").Element("outputs").Elements("variable");
                        foreach (var tmp in exp_out)
                        {
                            Variable reg_tmp = ExtractVariable(tmp);
                            tproc_one.AddExpGlobVar(reg_tmp);
                            ExtractGlobal(tp_one, reg_tmp);
                        }

                        //extract expected return value
                        var exp_ret = global_var.testCase.Element("tc").Element("Exp-out").Element("return").Element("variable");

                        if (exp_ret != null)
                        {
                            Variable ret_tmp = ExtractVariable(exp_ret);
                            Logger.WriteToLog(1, "ret value: " + ret_tmp.name);
                            tproc_one.SetRetVal(ret_tmp);
                        } 
                    }                                    
                }
            return 0;
        }

        /// <summary>
        /// Extracts the global from variable var.
        /// </summary>
        /// <param name="tp_one">The tp_one.</param>
        /// <param name="var">The variable.</param>
        private static void ExtractGlobal(TP tp_one, Variable var)
        {
            if ("reg".Equals(var.form))
                tp_one.AddRegister(var);
            if ("gvar".Equals(var.form))
                tp_one.AddGlobVar(var);
        }

        /// <summary>
        /// Extracts the return operation from XML element.
        /// </summary>
        /// <param name="tp_one">The tp_one.</param>
        /// <param name="stub_name">The stub_name.</param>
        /// <param name="tmp">The temporary.</param>
        /// <returns></returns>
        private static Return ExtractReturn(TP tp_one, string stub_name, XElement tmp)
        {
            Variable var_tmp = ExtractValues(tmp);
            int instance_tmp = 0;
            int.TryParse(tmp.Attribute("instance").Value, out instance_tmp);
            Return return_tmp = new Return(var_tmp, instance_tmp);
            Logger.WriteToLog(1, "Return added: " + var_tmp.name);
            if (instance_tmp > 0)
                tp_one.AddGlobCnt("COUNTER_" + stub_name);
            return return_tmp;
        }

        /// <summary>
        /// Extracts the check operation from XML element.
        /// </summary>
        /// <param name="tp_one">The tp_one.</param>
        /// <param name="stub_name">The stub_name.</param>
        /// <param name="tmp">The temporary.</param>
        /// <returns></returns>
        private static Check ExtractCheck(TP tp_one, string stub_name, XElement tmp)
        {
            Variable var_tmp = ExtractVariable(tmp);
            int instance_tmp = 0;
            int.TryParse(tmp.Attribute("instance").Value, out instance_tmp);                               
            Check check_tmp = new Check(var_tmp, instance_tmp);
            Logger.WriteToLog(1, "check added: " + var_tmp.name);
            if (instance_tmp > 0)
                tp_one.AddGlobCnt("COUNTER_" + stub_name);
            return check_tmp;
        }

        /// <summary>
        /// Extracts the modify operation from XML element.
        /// </summary>
        /// <param name="tp_one">The tp_one.</param>
        /// <param name="stub_name">The stub_name.</param>
        /// <param name="tmp">The temporary.</param>
        /// <returns></returns>
        private static Modify ExtractModify(TP tp_one, string stub_name, XElement tmp)
        {
            Variable var_tmp = ExtractVariable(tmp);
            //If tryParse is false then value should not change and remain zero
            int instance_tmp = 0;
            int.TryParse(tmp.Attribute("instance").Value, out instance_tmp);
            Modify modify_tmp = new Modify(var_tmp, instance_tmp);
            Logger.WriteToLog(1, "Modify added: " + var_tmp.name);
            if (instance_tmp > 0)
                tp_one.AddGlobCnt("COUNTER_" + stub_name);
            return modify_tmp;
        }

        /// <summary>
        /// Extracts the values from XML element and return them stored in variable.
        /// </summary>
        /// <param name="tmp">The temporary.</param>
        /// <returns></returns>
        private static Variable ExtractValues(XElement tmp)
        {
            if (tmp == null) { return null; }
            Variable var_tmp = new Variable();
            var_tmp.name = tmp.Value;
            var_tmp.type = tmp.Attribute("type").Value;
            var_tmp.value = tmp.Attribute("value").Value;
            if (tmp.Attribute("range") != null)
                var_tmp.range = tmp.Attribute("range").Value;
            return var_tmp;
        }

        /// <summary>
        /// Extracts the variable from XML element.
        /// </summary>
        /// <param name="tmp">The temporary.</param>
        /// <returns></returns>
        private static Variable ExtractVariable(XElement tmp)
        {
            if (tmp == null) { return null; }
            Variable var_tmp = ExtractValues(tmp);
            var_tmp.form = tmp.Attribute("form").Value;
            return var_tmp;
        }
    }
}
